/*************************************************************************
	> File Name: Input.hpp
	> Author: H.M. Lau
	> Mail: hm.lau@outlook.com
	> Created Time: 2023/10/06 17:45
 ************************************************************************/

#pragma once

#include <cstdint>
#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
#include "file_handler.hpp"
#include "log.hpp"

class Input
{
public:
    const static int _EOF = 0;

    Input ()
        :_next(_end)
        ,_smark(_end)
        ,_emark(_end)
        ,_pmark(_end)
        ,_plineno(0)
        ,_mline(1)
        ,_lineno(1)
        ,_eof_read(false)
        ,_plength(0)
        ,_filehandler(nullptr)
        ,_start_buf(new char(_buf_size))
    {}

    void newFile(const std::string& filename = "")
    {
        if (_filehandler) _filehandler->clear();
        else _filehandler = new FileHandler();
        _filehandler->open(filename);
        _eof_read = false;
        _next = _end;
        _smark = _end;
        _emark = _end;
        _pmark = _end;
        _end_buf = _end;
        _lineno = 1;
        _mline = 1;
    }

    std::string text() const 
    {
        char str[_buf_size];
        memset(&str, 0, sizeof str);
        strncpy(str, _start_buf + _smark, length());
        return std::string(str);
    }

    std::string ptext() const 
    {
        char str[_buf_size];
        memset(&str, 0, sizeof str);
        strncpy(str, _start_buf + _pmark, plength());
        return std::string(str);
    }
    
    int plength() const 
    { return _plength; }

    int plineno() const 
    { return _plineno; }

    int mark_start()
    {
        _mline = _lineno;
        _emark = _smark = _next;
        return _smark;
    }

    int mark_end()
    {
        _mline = _lineno;
        _emark = _next;
        return _emark;
    }

    bool pushBack(int n)
    {
        while (n-- && _next > _smark) 
            if (_start_buf[--_next] == '\n' || _start_buf[_next] == '\0') --_lineno;
        if (_next < _emark) {
            _emark = _next;
            _mline = _lineno;
        }
        return _next > _smark;
    }

    char lookAhead(int n)
    {
        uint8_t p = _start_buf[_next + n - 1];
        if (_eof_read && _next + n - 1 >= _end_buf) return EOF;
        return (_next + n - 1 < 0 || _next + n - 1 >= _end_buf) ? 0 : p;
    }

    int to_mark()
    {
        _lineno = _mline;
        _next = _emark;
        return _next;
    }

    int mark_prev()
    {
        _pmark = _smark;
        _plineno = _lineno;
        _plength = _emark - _smark;
        return _pmark;
    }

    char advance()
    {
        if (noMoreChars()) return 0;
        else if (_eof_read == false && flush(false) < 0) return -1;
        else if (_start_buf[_next] == '\n') ++_lineno;
        return _start_buf[_next++];
    }

    int length() const 
    { return _emark - _smark; }

private:
     int fillBuf(int starting_at)
    {
        int got = 0;
        int need = ((_end - starting_at) / _maxlex) * _maxlex;
        if (need < 0) log::logMessage(log::LOG_LEVEL::_ERROR, "Internal Error, fillBuf:Bad read-request starting addr.");
        else if ((got = _filehandler->read(_start_buf, starting_at, need)) == -1) log::logMessage(log::LOG_LEVEL::_ERROR, "Can not read input file.");
        _end_buf = starting_at + got;
        if (got < need) _eof_read = true;
        return got;
    }  

    int flush(bool force)
    {
        int copy_amt, shift_amt, left_edge;
        if (noMoreChars()) return NO_MORE_CHARS_TO_READ;
        else if (_eof_read) return FLUSH_OK;
        else if (_next > _danger || force) {
            left_edge = _pmark < _smark ? _pmark : _smark;
            shift_amt = left_edge;
            if (shift_amt < _maxlex) {
                if (!force) return FLUSH_FAIL;
                left_edge = mark_start();
                mark_prev();
                shift_amt = left_edge;
            }
            copy_amt = _end_buf - left_edge;
            for (int i = 0; i < copy_amt; ++i) _start_buf[i] = _start_buf[left_edge + i];
            if (fillBuf(copy_amt) == 0) log::logMessage(log::LOG_LEVEL::_ERROR, "Internal Error, _flush:Buffer is full, can not read.");
            else if (_pmark != 0) _pmark -= shift_amt;
            _smark -= shift_amt;
            _emark -= shift_amt;
            _next -= shift_amt;
        }
        return FLUSH_OK;
    }

    bool noMoreChars()
    { return _eof_read && _next >= _end_buf; }

    const int _maxlook = 16;
    const int _maxlex = 1024;
    const int _buf_size = (_maxlex * 3) + (_maxlook * 2);
    int _end_buf = _buf_size;
    const int _danger = _end_buf - _maxlook;
    const int _end = _buf_size;
    char* _start_buf;
    int _next;
    int _smark;
    int _emark;
    int _pmark;
    int _plineno;
    int _plength;
    FileHandler* _filehandler;
    int _lineno;
    int _mline;
    bool _eof_read;

    static int NO_MORE_CHARS_TO_READ;
    static int FLUSH_OK;
    static int FLUSH_FAIL;
};
int Input::NO_MORE_CHARS_TO_READ = 0;
int Input::FLUSH_OK = 1;
int Input::FLUSH_FAIL = -1;
